/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2018-2020.
 * Description: init rtos_print_add_timestamp for show timestamp
 * Author: nixiaoming
 * Create: 2018-3-2
 */

#include <linux/sched/clock.h>
#include <linux/printk.h>
#include <linux/preempt.h>
#include <linux/rtc.h>
#include <linux/time64.h>
#include <linux/device.h>

#include <linux/rtos_time.h>

#ifdef CONFIG_RTOS_MIN_DELTA_NS_ADJUST
#include "tick-internal.h"
#ifdef CONFIG_RTOS_MIN_DELTA_NS_ADJUST_INTERFACE
#include <internal/rtos_time_internal.h>
#endif
#endif

#ifdef CONFIG_RTOS_PRINT_UTC
char *get_time_str(char *buf, size_t buf_size)
{
	struct timespec64 ts;
	struct rtc_time tm;
	int ret;

	if (unlikely(buf == NULL || buf_size < RTOS_TIME_BUF_LEN))
		return NULL;

	ktime_get_real_ts64(&ts);

	rtc_time64_to_tm(ts.tv_sec, &tm);

	preempt_disable();
	ret = snprintf(buf, buf_size, "[UTC][%04d.%02d.%02d %02d:%02d:%02d-%lu][Clock][%llu]\n",
		tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
		tm.tm_sec, ts.tv_nsec, sched_clock());
	preempt_enable();

	return ((size_t)ret >= buf_size) ? NULL : buf;
}
EXPORT_SYMBOL(get_time_str);

void rtos_print_add_timestamp(char *info)
{
	char buf[RTOS_TIME_BUF_LEN] = {0};

	if (info == NULL)
		printk(KERN_INFO "%s\n", get_time_str(buf, sizeof(buf)));
	else
		printk(KERN_INFO "%s%s\n", get_time_str(buf, sizeof(buf)), info);
}
EXPORT_SYMBOL(rtos_print_add_timestamp);
#endif

#ifdef CONFIG_RTOS_MIN_DELTA_NS_ADJUST
struct clock_events_delta_ns {
	u64 default_min_delta_ns;
	u64 config_min_delta_ns;
};
static struct clock_events_delta_ns ce_delta_ns[NR_CPUS];
static DEFINE_RAW_SPINLOCK(tick_device_lock);

/**
 * set the min_delta_ns, this interface only can be used in module.
 */
#define MAX_DELTA_NS_LIMIT (NSEC_PER_SEC / HZ)
int set_min_delta_ns(u64 delta, int cpu)
{
	struct tick_device *td = NULL;
	unsigned long flags;
	if (!cpumask_test_cpu(cpu, cpu_online_mask)) {
		printk(KERN_ERR "The cpu %d is not in  online cpumask.\n", cpu);
		return -EINVAL;
	}

	/* when the delta under 1000, it may be noisy */
	if ((delta < 1000) || (delta > MAX_DELTA_NS_LIMIT)) {
		printk(KERN_ERR "The delta is %llu, must be between 1000(min value) and %lu(max limit).\n",
			delta, MAX_DELTA_NS_LIMIT);
		return -EINVAL;
	}

	raw_spin_lock_irqsave(&tick_device_lock, flags);
	td = tick_get_device(cpu);
	/* save the system default value */
	if (unlikely(!ce_delta_ns[cpu].default_min_delta_ns))
		ce_delta_ns[cpu].default_min_delta_ns = td->evtdev->min_delta_ns;
	/* just can set the value bigger than min value, include default value. */
	if (delta >= ce_delta_ns[cpu].default_min_delta_ns && delta <= td->evtdev->max_delta_ns) {
		ce_delta_ns[cpu].config_min_delta_ns = td->evtdev->min_delta_ns = delta;
		raw_spin_unlock_irqrestore(&tick_device_lock, flags);
		pr_info("clockevent: %s set min_delta_ns to %lluns on CPU%d.\n",
			current->comm, delta, cpu);
		return 0;
	} else {
		raw_spin_unlock_irqrestore(&tick_device_lock, flags);
		printk(KERN_ERR "The delta is %llu, must be between %llu(system default value) and %lu(max limit).\n",
			delta, ce_delta_ns[cpu].default_min_delta_ns, MAX_DELTA_NS_LIMIT);
		return -EINVAL;
	}
}
EXPORT_SYMBOL_NS(set_min_delta_ns, HW_RTOS_NS);

#ifdef CONFIG_RTOS_MIN_DELTA_NS_ADJUST_INTERFACE
static int __init init_min_delta_ns(char *param)
{
	u64 min_delta_ns;
	int i, ret;

	ret = kstrtoull(param, 10, &min_delta_ns);
	if (ret) {
		pr_err("early_param: parsing min_delta_ns failed! ret = %d\n", ret);
		return ret;
	}
	for (i = 0; i < sizeof(ce_delta_ns) / sizeof(struct clock_events_delta_ns); ++i)
		ce_delta_ns[i].config_min_delta_ns = min_delta_ns;
	pr_info("early_param: set min_detal_ns to %llu\n", min_delta_ns);

	return 0;
}
early_param("min_delta_ns", init_min_delta_ns);

static int set_restore_min_delta_ns(u64 delta, int cpu)
{
	/* 0 indicate us to restore the default min_delta_ns */
	if (delta == 0) {
		if (ce_delta_ns[cpu].default_min_delta_ns &&
			ce_delta_ns[cpu].default_min_delta_ns != ce_delta_ns[cpu].config_min_delta_ns) {
			delta = ce_delta_ns[cpu].default_min_delta_ns;
			pr_info("clockevent: restore min_delta_ns of CPU%d\n", cpu);
		} else {
			pr_info("clockevent: min_delta_ns is already default.\n");
			return 0;
		}
	}

	return set_min_delta_ns(delta, cpu);
}

/**
 * adjust_min_delta_ns - adjust per-cpu clock_event_device->min_delta_ns
 *  while cpu online.
 *
 * We don't protect the ce_delta_ns by using spin_lock_irq, so this function could only
 * called from cpu hot-plug starup callback. ced == this_cpu_ptr(tick_cpu_device)->evtdev.
 */
void adjust_min_delta_ns(struct clock_event_device *ced)
{
	int cpu = smp_processor_id();
	if (!ce_delta_ns[cpu].config_min_delta_ns ||
		ce_delta_ns[cpu].config_min_delta_ns == ced->min_delta_ns)
		return;

	if (unlikely(ce_delta_ns[cpu].default_min_delta_ns != ced->min_delta_ns)) {
		/* first time to startup or default min_delta_ns has changed. */
		if (ce_delta_ns[cpu].default_min_delta_ns)
			pr_info("default min_delta_ns has changed to %llu.\n", ced->min_delta_ns);
		ce_delta_ns[cpu].default_min_delta_ns = ced->min_delta_ns;
	}

	if (ce_delta_ns[cpu].config_min_delta_ns < ced->min_delta_ns ||
		ce_delta_ns[cpu].config_min_delta_ns > MAX_DELTA_NS_LIMIT) {
		pr_err("clockevent: CPU%d min_delta_ns out of range [%llu, %llu], early_param set to %llu\n",
			cpu, ced->min_delta_ns, (u64)MAX_DELTA_NS_LIMIT, ce_delta_ns[cpu].config_min_delta_ns);
		return;
	}

	ced->min_delta_ns = ce_delta_ns[cpu].config_min_delta_ns;
	pr_info("CPU%d hotplug: adjust min_delta_ns to %llu\n", cpu, ce_delta_ns[cpu].config_min_delta_ns);
}

static ssize_t min_delta_ns_show(struct device *dev,
				struct device_attribute *attr,
				char *buf)
{
	struct tick_device *td;
	ssize_t count = 0;

	td = tick_get_device(dev->id);
	if (td && td->evtdev)
		count = snprintf(buf, PAGE_SIZE, "%llu\n", td->evtdev->min_delta_ns);

	return count;
}

static ssize_t min_delta_ns_store(struct  device *dev,
				struct device_attribute *attr,
				const char *buf, size_t count)
{
	unsigned long long min_delta_ns = 1;
	struct tick_device *td;
	int ret = 0;

	ret = kstrtoull(buf, 10, &min_delta_ns);
	if (ret < 0)
		return ret;
	td = tick_get_device(dev->id);
	if (td && td->evtdev)
		ret = set_restore_min_delta_ns(min_delta_ns, dev->id);
	else
		pr_err("clockevent: no clockevent_device on cpu %d\n", dev->id);

	return ret < 0 ? ret : (ssize_t)count;
}
static DEVICE_ATTR_RW(min_delta_ns);

int create_sysfs_min_delta_ns(struct device *dev)
{
	int ret = device_create_file(dev, &dev_attr_min_delta_ns);
	if (ret)
		pr_err("create sysfs entry failed! error = %d\n", ret);

	return ret;
}
#endif
#endif
